將屬性命名以單下劃線開頭,利用這種方式來暗示屬性是受保護的,不建議外界直接訪問。如果想訪問屬性可以透過屬性的 getter(訪問器)和 setter(修改器)方法進行操作。可以使用 @property 來包裝 getter 和 setter 方法,使得屬性的訪問安全又方便。
class Person(object):
def __init__ (self, name, age):
self._name = name
self._age = age
# getter
@property
def name(self):
return self._name
# getter
@property
def age(self):
return self._age
# setter
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s 正在玩手機' % self._name)
else:
print('%s 正在玩電腦' % self._name)
def main():
person = Person('Andy', 12)
person.play()
person.age = 22
person.play()
if __name__ == '__main__':
main()
動態語言允許我們在程序運行時給 object 綁定新的屬性或 method,也可以對已經綁定的屬性和 method 解綁定。
如果需要限定自定義類型的 object 只能綁定某些屬性,可以利用 __ slots __ 變數來進行限定。 __ slots __ 的限定只對當前 class 的 object 生效,對子類別並不作用。
class Person(object):
# 限定 Person object 只能绑定 _name, _age 和 _gender 屬性
__slots__ = ('_name', '_age', '_gender')
def __init__ (self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age (self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s 正在玩手機' % self._name)
else:
print('%s 正在玩電腦' % self._name)
def main():
person = Person('Andy', 22)
person.play()
person._gender = '男'
# person._is_gay = True 沒有在 __slots__ 中就會得到錯誤
# AttributeError: 'Person' object has no attribute '_is_gay'
if __name__ == '__main__':
main()
在 class 中的 method 並不需要都是物件 (object) method。
例如定義一個'三角形'class,透過傳入三條邊長來構成三角形,並提供計算周長和面積 method。
但傳入的三條邊長未必能構造出三角形 object,因此先寫一個 method 來驗證三條邊長是否可以構成三角形。
在調用這個 method 時三角形 object 尚未創建,所以這個 method 是屬於三角形 class 而並不屬於三角形 object 。可以使用靜態 method 來解決這類問題。
from math import sqrt
class Triangle(object):
def __init__ (self, a, b, c):
self._a = a
self._b = b
self._c = c
# 使用 @staticmethod 不需要實例化,直接使用 class.method() 名來調用
@staticmethod
def is_valid (a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 靜態 method 和 class method 都是透過给 class 發消息來調用
if Triangle.is_valid (a, b, c):
t = Triangle (a, b, c)
# 也可以透過給 class 發消息來調用 object method,
# 但是要傳入接收消息的 object 作为參數
print(Triangle.perimeter(t))
print(Triangle.area(t))
print(t.perimeter())
print(t.area())
else:
print('無法構成三角形.')
if __name__ == '__main__':
main()
Python 還可以在 class 中定義 class method,class method 的第一個參數約定名為 cls,它代表的是當前 class 相關的信息的 object(class 本身也是一個 object,有的地方也稱之為 class 的元數據 object)通過這個參數我們可以獲取和 class 相關的信息並且可以創建出 class 的 object。
from time import time, localtime, sleep
class Clock(object):
def __init__(self, hour = 0, minute = 0, second = 0):
self._hour = hour
self._minute = minute
self._second = second
# 使用 @ classmethod 不需要實例化,直接使用 class.method() 名來調用
@ classmethod
def now(cls):
ctime = localtime(time()) # localtime()函數,作用是格式化時間為本地的時間
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
# 透過 class method 創建 object 並獲取系統時間
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
time 模組中,可以使用 索引和屬性訪問 time.struct_time object 的值。
簡單的說,類和類之間的關係有三種:is-a、has-a 和 use-a 關係。
可以在已有 class 的基礎上創建新 class ,這其中的一種做法就是讓一個 class 從另一個 class 那將屬性和 method 直接繼承下來。提供繼承信息的我們稱之為父類別 (class),得到繼承信息的我們稱之為子類別 (class)。
子類別 (class) 除了繼承父類別 (class) 提供的屬性和 method,還可以定義自己特有的屬性和 method,所以子類別 (class) 比父類別 (class) 擁有的更多的能力。
class Person(object): # 父類別 (class) 人
def __init__ (self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍' % self._name)
def watch_movie(self):
if self._age >= 18:
print('%s 能觀看所有電影' % self._name)
else:
print('%s 只能觀看普通電影' % self._name)
class Student(Person): # 子類別 (class) 學生
def __init__ (self, name, age, grade):
super().__init__ (name, age) # 繼承父類別 (class)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade (self, grade):
self._grade = grade
def study (self, course):
print('%s的 %s 正在學習%s' % (self._grade, self._name, course))
class Teacher(Person): # 子類別 (class) 老師
def __init__ (self, name, age, title):
super().__init__ (name, age) # 繼承父類別 (class)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title (self, title):
self._title = title
def teach (self, course):
print('%s %s正在講 %s' % (self._name, self._title, course))
def main():
stu = Student('Andy', 15, '國三')
stu.study('數學')
stu.watch_movie()
t = Teacher('Alan', 38, '老師')
t.teach('Python 程式設計')
t.watch_movie()
if __name__ == '__main__':
main()
子類別 (class) 繼承父類別 (class) 的 method 後,可以對父類別 (class) 已有的 method 給出新的版本,這個動作稱之為方法 (method) 覆寫 (override)。
透過方法 (method) 覆寫 (override) 我們可以讓父類別 (class) 的同一個行為在子類別 (class) 中擁有不同的實現版本,當我們調用這個經過子類別 (class) 覆寫 (override) 的 method 時,不同的子類別 (class) object 會表現出不同的行為,這個就是多型 (poly-morphism)。
將 Pet class 處理成了一個抽象 class,所謂抽象 class 就是不能夠創建 object 的 class,這種 class 就是專門讓其他 class 去繼承它。
利用 abc 模組的 ABCMeta 元類別 (class) 和 abstractmethod 包裝器來達到抽象 class 的效果,如果一個 class 中存在抽象 method 那麼這個 class 就不能夠實例化(創建 object)。
Dog 和 Cat兩個子類別 (class) 分別對 Pet class 中的 make_voice 抽象方法進行覆寫 (override) 並給出了不同的實現版本,當我們在 main 函數中調用該 method 時,這個 method 就表現出多型 (polymorphism),也就是同樣的 method 做了不同的事情。
from abc import ABCMeta, abstractmethod
class Pet (object, metaclass = ABCMeta): # 抽象父類別 (class) 寵物
def __init__ (self, nickname):
self._nickname = nickname
@abstractmethod
def make_voice(self):
pass
class Dog(Pet): # 子類別 (class) 狗
def make_voice(self): # 繼承抽象父類別 (class) 並覆寫
print('%s : 汪汪汪...' % self._nickname)
class Cat(Pet): # 子類別 (class) 貓
def make_voice(self): # 繼承抽象父類別 (class) 並覆寫
print('%s : 喵...喵...' % self._nickname)
def main():
pets = [Dog('小黑'), Cat('小喵'), Dog('小白')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
練習1 - 超人打怪獸
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter (object, metaclass = ABCMeta): # 抽象父類別 (class) 攻擊
# 利用__slots__ 限定 object 可以绑定的成員變數
__slots__ = ('_name', '_hp')
def __init__ (self, name, hp): # 初始化(名字, 生命值)
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
@property
def alive(self):
return self._hp > 0
@abstractmethod
def attack(self, other): # 攻擊(對象)
pass
class Ultraman(Fighter): # 子類別 (class) 超人
__slots__ = ('_name', '_hp', '_mp')
def __init__ (self, name, hp, mp): # 初始化(名字, 生命值, 魔法值)
super().__init__ (name, hp) # 繼承抽象父類別 (class)
self._mp = mp
def attack (self, other): # 繼承抽象父類別 (class) 並覆寫
other.hp -= randint(15, 25)
def huge_attack (self, other): # 必殺攻擊(對象)
if self._mp >= 50:
self._mp -= 50
injury = other.hp * 3 // 4
injury = injury if injury >= 50 else 50
other.hp -= injury
return True # 成功
else:
self.attack(other)
return False
def magic_attack (self, others): # 魔法攻擊(對象)
if self._mp >= 20:
self._mp -= 20
for temp in others:
if temp.alive:
temp.hp -= randint(10, 15)
return True
else:
return False
def resume(self): # 恢復魔法
incr_point = randint(1, 10)
self._mp += incr_point
return incr_point
def __str__(self):
return '~~~ %s 超人 ~~~\n' % self._name + \
'生命值: %d\n' % self._hp + \
'魔法值: %d\n' % self._mp
class Monster(Fighter): # 子類別 (class) 怪獸
__slots__ = ('_name', '_hp')
def attack(self, other): # 繼承抽象父類別 (class) 並覆寫
other.hp -= randint(10, 20)
def __str__(self):
return '~~~ %s 怪獸 ~~~\n' % self._name + \
'生命值: %d\n' % self._hp
def is_any_alive(monsters): # 判斷有没有怪獸活著
for monster in monsters:
if monster.alive > 0:
return True
return False
def select_alive_one(monsters): # 選中一隻活著的怪獸
monsters_len = len(monsters)
while True:
index = randrange(monsters_len)
monster = monsters[index]
if monster.alive > 0:
return monster
def display_info(ultraman, monsters): # 顯示訊息
print(ultraman)
for monster in monsters:
print(monster, end='')
def main():
u = Ultraman('Andy', 1000, 120)
m1 = Monster('monsterA', 250)
m2 = Monster('monsterB', 500)
m3 = Monster('monsterC', 750)
ms = [m1, m2, m3]
fight_round = 1
while u.alive and is_any_alive(ms):
print('======== 第 %02d 回合 ========' % fight_round)
m = select_alive_one(ms) # 選中一隻活著的怪獸
skill = randint(1, 10) # 隨機選擇使用技能
if skill <= 6: # 60% 的機率使用普通攻擊
print('%s 使用普通攻击打了 %s' % (u.name, m.name))
u.attack(m)
print('%s 的魔法值恢復了 %d 點' % (u.name, u.resume()))
elif skill <= 9: # 30% 的機率使用魔法攻擊(可能因魔法值不足而失敗)
if u.magic_attack(ms):
print('%s 使用了魔法攻擊' % u.name)
else:
print('%s 使用魔法失敗' % u.name)
else: # 10% 的機率使用必殺攻擊(如果魔法值不足則使用普通攻擊)
if u.huge_attack(m):
print('%s 使用必殺攻擊打了 %s' % (u.name, m.name))
else:
print('%s 使用普通攻擊打了 %s' % (u.name, m.name))
print('%s 的魔法值恢復了 %d 點' % (u.name, u.resume()))
if m.alive > 0: # 如果怪獸没有死就回擊超人
print('%s 回擊 %s' % (m.name, u.name))
m.attack(u)
display_info(u, ms) # 每個回合結束顯示訊息
fight_round += 1
print('\n======== 戰鬥结束! ========\n')
if u.alive > 0:
print('%s 超人勝利!' % u.name)
else:
print('怪獸勝利!')
if __name__ == '__main__':
main()
這個練習很特別,當中有很多樂趣,利用隨機的值,大戰了 66 回合。
練習2 - 撲克牌遊戲
import random
class Card(object): # 一張牌
def __init__(self, suite, face): # 初始化(花色,數字)
self._suite = suite
self._face = face
@property
def face(self):
return self._face
@property
def suite(self):
return self._suite
def __str__(self):
if self._face == 1:
face_str = 'A'
elif self._face == 11:
face_str = 'J'
elif self._face == 12:
face_str = 'Q'
elif self._face == 13:
face_str = 'K'
else:
face_str = str(self._face)
return '%s%s' % (self._suite, face_str)
def __repr__(self):
return self.__str__()
class Poker(object): # 一副牌
def __init__(self):
self._cards = [Card(suite, face)
for suite in '♠♥♣♦'
for face in range(1, 14)]
self._current = 0
@property
def cards(self):
return self._cards
def shuffle(self): # 洗牌(隨機)
self._current = 0
random.shuffle(self._cards)
@property
def next(self): # 發牌
card = self._cards[self._current]
self._current += 1
return card
@property
def has_next(self): # 還有沒有牌
return self._current < len(self._cards)
class Player(object): # 玩家
def __init__(self, name):
self._name = name
self._cards_on_hand = []
@property
def name(self):
return self._name
@property
def cards_on_hand(self):
return self._cards_on_hand
def get(self, card): # 拿牌
self._cards_on_hand.append(card)
def arrange(self, card_key): # 整理牌
self._cards_on_hand.sort(key = card_key)
# 排序規則 - 先根據花色再根據大小排序
def get_key(card):
return (card.suite, card.face)
def main():
p = Poker()
p.shuffle()
players = [Player('pA'), Player('pB'), Player('pC'), Player('pD')]
for _ in range(13):
for player in players:
player.get(p.next)
for player in players:
print(player.name + ' :', end =' ')
player.arrange(get_key)
print(player.cards_on_hand)
if __name__ == '__main__':
main()
練習3 - 工資结算系统
某公司有是部門經理、程序員和銷售員,根據提供的員工訊息來計算月薪。
部門經理的月薪是每月固定 45000 元
程序員的月薪按本月工作時間計算每小時 200 元
銷售員的月薪是 22000 元的底薪加上銷售額 5% 的加成
from abc import ABCMeta, abstractmethod
class Employee (object, metaclass = ABCMeta): # 抽象父類別 (class) 員工
def __init__ (self, name):
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self): # 月薪
pass
class Manager(Employee): # 子類別 (class) 部門經理
def get_salary(self): # 繼承抽象父類別 (class) 並覆寫
return 45000.0
class Programmer(Employee): # 子類別 (class) 程序員
def __init__(self, name, working_hour = 0):
super().__init__(name) # 繼承抽象父類別 (class)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self): # 繼承抽象父類別 (class) 並覆寫
return 200.0 * self._working_hour
class Salesman(Employee): # 子類別 (class) 銷售員
def __init__(self, name, sales = 0):
super().__init__(name) # 繼承抽象父類別 (class)
self._sales = sales
@property
def sales(self):
return self._sales
@sales.setter
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self): # 繼承抽象父類別 (class) 並覆寫
return 22000.0 + self._sales * 0.05
def main():
emps = [
Manager('Alan'), Programmer('Andy'),
Manager('Adam'), Salesman('Alex'),
Salesman('Alice'), Programmer('Angle'),
Programmer('Amy')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('請輸入 %s 本月工作時數: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('請輸入 %s 本月銷售額: ' % emp.name))
# 同樣是 get_salary 但不同的職位表現出不同的行為(多型)
print('%s 本月工資為: %s 元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()